package edu.siu.math.egut.egut;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;


/**
 * This is the egut interpreter. It uses LiE to handle all underlying
 * operations, so all LiE commands are valid.
 * 
 * @author Joe Pleso
 * 
 */
public class Egut {

    private static final String EGUT_TOKEN = "egut";
    private static final String CLASS_PREFIX = "edu.siu.math.egut.functions.";
    private static final String MISSING_ARGUMENT_MESSAGE_1 = "Arguments required for ";
    private static final String MISSING_ARGUMENT_MESSAGE_2 = "Valid argument types are for instance: ";
    private static final String MISSING_ARGUMENT_MESSAGE_3 = ".";
    private static final String TOO_MANY_ARGUMENTS_MESSAGE = "Too many arguments for ";

    /**
     * The entry point for the application.
     * 
     * @param args          these are ignored
     * @throws InterruptedException       we were interrupted by something else
     * 
     */

    public static void main(String[] args) throws InterruptedException {

	Process lieProcess = Algorithms.getNewLiEProcess(true);

	try {
	    initLoop(new BufferedWriter(new OutputStreamWriter(lieProcess
		    .getOutputStream())), new BufferedReader(
		    new InputStreamReader(lieProcess.getInputStream())), hook());
	} catch (IOException e) {

	    e.printStackTrace();
	} catch (InstantiationException e) {
	    // Should never happen
	    throw new RuntimeException(e);
	} catch (IllegalAccessException e) {
	    // Should never happen
	    throw new RuntimeException(e);
	}
    }

    private static void initLoop(BufferedWriter lieIn, BufferedReader lieOut,
	    int i) throws IOException, InterruptedException, InstantiationException, IllegalAccessException {
	boolean flag = true;
	StringBuffer syntax;
	while (true) {
	    try {
		while (i != -1) {
		    if (i != Algorithms.LIE_PROMPT_1)
			i = hook();
		    StringBuffer sb = new StringBuffer();
		    while (i == Algorithms.LIE_PROMPT_1
			    || i == Algorithms.LIE_PROMPT_2) {
			System.out.print((char) i);
			i = System.in.read();
			flag = true;
			while (i != Algorithms.LIE_NEWLINE) {
			    System.out.print((char) i);
			    sb.append((char) i);
			    i = System.in.read();
			}
			System.out.print((char) i);
			sb.append((char) i);
			i = System.in.read();
			if (i == Algorithms.LIE_PARTING_MESSAGE.charAt(0)) {
			    endEgut();
			}
			if (i == Algorithms.LIE_PROMPT_1) {
			    flag = false;
			    break;
			}
		    }// end while (i == Algorithms.LIE_PROMPT_1 || i == Algorithms.LIE_PROMPT_2)
		    syntax = new StringBuffer();
		    while (flag) {
			if (i == Algorithms.LIE_NEWLINE) {
			    i = System.in.read();
			    if (i == Algorithms.LIE_PROMPT_1)
				break;
			    else
				continue;
			}// end if i == NEWLINE
			syntax.append((char) i);
			i = System.in.read();
			// Consume lie output until prompt
		    }// end while flag
		    fixStringBuffer(sb);// Clean up escape codes and backspaces
		    if (syntax.indexOf("syntax") == -1)
			parse(sb, lieIn, lieOut);// process internal commands
		    if (flag)
			System.out.println(runLiECommand(sb.toString(), lieIn,
				lieOut));
		    else
			runLiECommand(sb.toString(), lieIn, lieOut);
		}// end of try block?

	    } catch (EgutMissingArgumentException e) {

		System.out.println(MISSING_ARGUMENT_MESSAGE_1
			+ e.getErrorClass().getSimpleName()
			+ MISSING_ARGUMENT_MESSAGE_2
			+ e.getErrorClass().getSimpleName()
			+ Algorithms.argList(((AbstractEgutFunction) e
				.getErrorClass().newInstance()).argList())
			+ MISSING_ARGUMENT_MESSAGE_3);

	    } catch (EgutTooManyArgumentsException e) {

		System.out.println(TOO_MANY_ARGUMENTS_MESSAGE
			+ e.getErrorClass().getSimpleName()
			+ MISSING_ARGUMENT_MESSAGE_2
			+ e.getErrorClass().getSimpleName()
			+ Algorithms.argList(((AbstractEgutFunction) e
				.getErrorClass().newInstance()).argList())
			+ MISSING_ARGUMENT_MESSAGE_3);

	    }// end last catch block

	}// end while true.
    }// end method initLoop

    private static void endEgut() {

	System.out.println(Algorithms.LIE_PARTING_MESSAGE);
	System.exit(0);

    }

    // Run cmd on lie and return the result
    // no checks are done here
    private static String runLiECommand(String s, BufferedWriter lieIn,
	    BufferedReader lieOut) throws IOException {

	StringBuffer sb = new StringBuffer();
	lieIn.write(s);
	lieIn.newLine();
	lieIn.write(Algorithms.LIE_PRINT_COMMAND
		+ Algorithms.LIE_LEFT_PARENTHESIS + Algorithms.LIE_QUOTE
		+ EGUT_TOKEN + Algorithms.LIE_QUOTE
		+ Algorithms.LIE_RIGHT_PARENTHESIS);
	lieIn.newLine();
	lieIn.flush();

	int j = sb.indexOf(EGUT_TOKEN);
	while (j == -1) {
	    sb.append((char) lieOut.read());

	    j = sb.indexOf(EGUT_TOKEN);
	    // Integer.toString(lieProcess.hashCode()));

	}
	lieOut.read();
	j = sb.lastIndexOf(Character.toString(Algorithms.LIE_NEWLINE), j);
	if (j == -1)
	    j++;
	return sb.substring(0, j);
    }

    private static void fixStringBuffer(StringBuffer sb) {

	boolean flag = false;

	for (int i = 0; i < sb.length(); i++) {
	    if (sb.charAt(i) == (char) 8) {
		sb.deleteCharAt(i - 1);
		sb.deleteCharAt(i - 1);
		flag = true;
		break;
	    }
	    if (sb.charAt(i) == (char) 27) {
		sb.deleteCharAt(i);
		sb.deleteCharAt(i);
		while ((int) sb.charAt(i) < 64 || (int) sb.charAt(i) > 126)
		    sb.deleteCharAt(i);
		sb.deleteCharAt(i);
		flag = true;
		break;
	    }
	}
	if (flag)
	    fixStringBuffer(sb);

    }

    private static int hook() throws IOException {

	int i;

	i = System.in.read();

	while (i != '>') {
	    System.out.print((char) i);
	    i = System.in.read();
	}
	return i;

    }

    private static void parse(StringBuffer s, BufferedWriter lieIn,
	    BufferedReader loop) throws EgutMissingArgumentException,
	    InterruptedException, IOException, EgutTooManyArgumentsException {

	int i = s.indexOf(EGUT_TOKEN);

	while (i != -1) {

	    int j;
	    for (j = i + EGUT_TOKEN.length(); j < s.length(); j++) {
		if (!Character.isLetter(s.charAt(j)))
		    break;
	    }
	    String sub = s.substring(i + EGUT_TOKEN.length(), j);
	    try {
		Class<?> c = Class.forName(CLASS_PREFIX + sub);

		if (AbstractEgutFunction.class.isAssignableFrom(c)) {
		    int k;
		    for (k = j; k < s.length(); k++) {
			if (!Character.isWhitespace(s.charAt(k))) {
			    if (s.charAt(k) == '(')
				break;
			    else
				throw new EgutMissingArgumentException(c);
			}

		    }
		    int m;
		    boolean isQuoted = false;
		    boolean isComment = false;
		    int leftCount = 0;
		    for (m = k; m < s.length(); m++) {
			if (isQuoted) {
			    if (s.charAt(m) == Algorithms.LIE_QUOTE)
				isQuoted = false;
			} else if (isComment) {
			    if (s.charAt(m) == Algorithms.LIE_COMMENT)
				isComment = false;
			} else {
			    if (s.charAt(m) == Algorithms.LIE_LEFT_PARENTHESIS)
				leftCount++;
			    else if (s.charAt(m) == Algorithms.LIE_RIGHT_PARENTHESIS)
				leftCount--;
			    else if (s.charAt(m) == Algorithms.LIE_COMMENT)
				isComment = true;
			    else if (s.charAt(m) == Algorithms.LIE_QUOTE)
				isQuoted = true;

			    if (leftCount == 0)
				break;
			}
		    }

		    // Now we know that we have a function to replace
		    // we can get the function from c
		    // and the arguments from k+1 to m

		    AbstractEgutFunction aef = (AbstractEgutFunction) c
			    .newInstance();

		    String[] args = split(s.substring(k + 1, m),
			    aef.argList().length);
		    if (args == null)
			throw new EgutTooManyArgumentsException(c);
		    if (args[args.length - 1] == null)
			args[args.length - 1] = getDefaultGroup(lieIn, loop)
				.toString();

		    for (int n = 0; n < args.length; n++) {
			StringBuffer sb = new StringBuffer();
			sb.append(args[n]);
			parse(sb, lieIn, loop);

			args[n] = runLiECommand(sb.toString(), lieIn, loop);
		    }

		    Thread t = new Thread(aef);

		    aef.setData(args);
		    t.start();
		    t.join();
		    String replacement = aef.getRawData()[0];
		    s.delete(i, m + 1);
		    s.insert(i, replacement);

		}

	    } catch (ClassNotFoundException e) {

	    } catch (InstantiationException e) {

	    } catch (IllegalAccessException e) {

	    } finally {

		i = s.indexOf(EGUT_TOKEN, i + 1);
	    }
	}// while

    }

    private static String[] split(String s, int help) {
	MatchParen mp = new MatchParen();
	boolean isQuoted = false;
	boolean isComment = false;
	String[] result = new String[help];
	int i = 0;
	int count = 0;
	int last = i;
	while (i < s.length()) {
	    if (isQuoted) {
		if (s.charAt(i) == Algorithms.LIE_QUOTE)
		    isQuoted = false;
		i++;

	    } else if (isComment) {
		if (s.charAt(i) == Algorithms.LIE_COMMENT)
		    isComment = false;
		i++;
	    } else {
		mp.add(s.charAt(i));
		if (s.charAt(i) == Algorithms.LIE_QUOTE)
		    isQuoted = true;
		else if (s.charAt(i) == Algorithms.LIE_COMMENT)
		    isComment = true;
		else {

		    if (mp.isMatching() && s.charAt(i) == Algorithms.LIE_COMMA) {
			if (count == help - 1)
			    return null;
			result[count] = (s.substring(last, i));
			count++;
			last = i + 1;
		    }
		}
		i++;
	    }
	}

	result[count] = s.substring(last, s.length());
	return (String[]) result;
    }

    private static LiEGroup getDefaultGroup(BufferedWriter lieIn,
	    BufferedReader lieOut) throws IOException {
	runLiECommand(Algorithms.LIE_DEFAULT_GROUP_FUNCTION_DEFINITION, lieIn,
		lieOut);
	String group = runLiECommand(Algorithms.LIE_DEFAULT_GROUP_FUNCTION,
		lieIn, lieOut);
	if (group.charAt(0) == Algorithms.LIE_N)
	    try {
		throw new NoDefaultGroupException();
	    } catch (NoDefaultGroupException e) {

		e.printStackTrace();
	    }

	return new LiEGroup(group);
    }

}
